/*************************************************************************
	> File Name: myled.c
	> Author: life
	> Created Time: 2021年03月27日 星期六 19时02分23秒
 ************************************************************************/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/moduleparam.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>



#define BCM2711_GPIO_BASE   0xfe200000
#define DEVICE_NAME     "leds"  /* 加载模式后，执行”cat /proc/devices”命令看到的设备名称 */
#define LED_MAJOR       231     /* 主设备号 */
static unsigned * gpio_va;

spinlock_t leds_lock;

static struct class *leds_class;
static struct device *leds_class_devs[4];

static char leds_status = 0x0; 

#define GPFSEL0 (0x0/4)
#define GPFSEL1 (0x4/4)
#define GPFSEL2 (0x8/4)
#define GPFSEL3 (0xc/4)
#define GPFSEL4 (0x10/4)
#define GPFSEL5 (0x14/4)
#define GPSET0 (0x1c/4)
#define GPSET1 (0x20/4)
#define GPCLR0 (0x28/4)
#define GPCLR1 (0x2c/4)


static int rpi4_leds_open(struct inode *inode, struct file *file)
{
	int sel0_value = 0;
	
	spin_lock_init(&leds_lock);
	//实现对pin 4引脚 设为输出操作
	sel0_value = *(gpio_va+GPFSEL0);	
	sel0_value |= (1<<12);
	sel0_value &= ~(1<<13);
	sel0_value &= ~(1<<14);
	*(gpio_va+GPFSEL0) = sel0_value;

	printk(KERN_INFO"led_open sel0_value = 0x%x\n", sel0_value);
    return 0;
}

static ssize_t rpi4_leds_read (struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
	int val;
	int ret =0;
	spin_lock(&leds_lock);
	val = leds_status & 0x1;
	spin_unlock(&leds_lock);
	ret = copy_to_user(buff, (const void *)&val, 1);
	if (ret < 0)
	{
		printk("copy_to_user faild\n");
		return -1;
	}
	return 0;
}

static ssize_t rpi4_leds_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
	int set0_value = 0;
	char val = 0;
	int ret =0;
	ret = copy_from_user((void *)&val, buf, 1);
	if (ret < 0)
	{
		printk("copy_from_user faild\n");
		return -1;
	}
	if (val > 0)
	{
		set0_value = *(gpio_va+GPSET0);
		set0_value |= (1<<4);
		*(gpio_va+GPSET0) = set0_value;
		printk(KERN_ERR"0 write led_value =0x%x \n",set0_value);
	}
	else 
	{
		set0_value = *(gpio_va+GPCLR0);
		set0_value |= (1<<4);
		*(gpio_va+GPCLR0) = set0_value;
		printk(KERN_ERR"1 write led_value =0x%x \n",set0_value);
	}
	return 0;
}


/* 这个结构是字符设备驱动程序的核心
 * 当应用程序操作设备文件时所调用的open、read、write等函数，
 * 最终会调用这个结构中指定的对应函数
 */
static struct file_operations rpi4_leds_fops = {
    .owner  =   THIS_MODULE,    /* 这是一个宏，推向编译模块时自动创建的__this_module变量 */
    .open   =   rpi4_leds_open,     
	.read	=	rpi4_leds_read,	   
	.write	=	rpi4_leds_write,	   
};


static int __init leds_init(void)
{
	 /* 注册字符设备
     * 参数为主设备号、设备名字、file_operations结构；
     * 这样，主设备号就和具体的file_operations结构联系起来了，
     * 操作主设备为LED_MAJOR的设备文件时，就会调用s3c24xx_leds_fops中的相关成员函数
     * LED_MAJOR可以设为0，表示由内核自动分配主设备号
     */
    int ret;
	int minor = 0; //主设备号
	
    gpio_va = ioremap(BCM2711_GPIO_BASE, 0x80);
	if (!gpio_va) {
		return -EIO;
	}
	printk (KERN_INFO"global_gpio = 0x%lx\n", (unsigned long)gpio_va);
	ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &rpi4_leds_fops);
	if (ret < 0) {
		printk(DEVICE_NAME " can't register major number\n");
		return ret;
    }

	ret = class_register(leds_class);
#if 0
	leds_class_devs[0] = device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds");

	for (minor = 1; minor < 4; minor++)
	{
		leds_class_devs[minor] = device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);
		if (unlikely(IS_ERR(leds_class_devs[minor])))
			return PTR_ERR(leds_class_devs[minor]);
	}

#endif
	printk(KERN_NOTICE" initialized\n");
	return 0;
}

static void __exit leds_exit(void)
{
	int minor = 0; //主设备号
	unregister_chrdev(LED_MAJOR, DEVICE_NAME);

	//class_unregister(leds_class);
	class_destroy(leds_class);
	iounmap(gpio_va);
	printk(KERN_NOTICE"leds_exit exit\n");
}
module_init(leds_init);
module_exit(leds_exit);

/* 描述驱动程序的一些信息，不是必须的 */
MODULE_AUTHOR("ChenBlue");
MODULE_VERSION("0.1.0");
MODULE_DESCRIPTION("Tiny4412 LED Driver");
MODULE_LICENSE("GPL");


